"""
利用数组进行数据处理
NumPy数组使你可以将许多数据处理任务表述为简洁的数组表达式否则需要编写循环 ,用数组表达式代替循环的做法,通常被称为矢量化
一般来说 矢量化数组运算要比等价的纯python方式快上一两个数量级 广播这是一种针对矢量化计算的强大手段

使用 matplotlib 创建二维数组可视化
将条件逻辑表述为数组运算
numpy.where 函数是三元表达式 x if condition else y 的矢量版本

# 假设我们想要在一组值(网格型)上计算函数 sqrt(x^2+y^2)
# np.meshgrid 函数将返回两个数组，分别包含 x 和 y 的值。 接受两个一维数组,并产生两个二维矩阵
# points = np.arange(-5, 5, 0.01)
# xs, ys = np.meshgrid(points, points)
# z = np.sqrt(xs ** 2 + ys ** 2)
# plt.imshow(z,cmap=plt.cm.gray)
# plt.colorbar()
# plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5]) #值数组
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5]) #值数组
cond = np.array([True, False, True, True, False]) # 布尔数组

# 要求 根据cond的值 选取xarr或yarr的值 当cond中的值为True时选取xarr的值,否则选取yarr的值
# result = [(x if c else y) for x,y,c in zip(xarr,yarr,cond)]
# 使用条件判断语句进行数组运算有几个问题 第一 它对大数据的处理速度很慢 第二 它不支持向量化运算 无法用于多维数组 因此 numpy.where 函数写的很简洁
result = np.where(cond, xarr, yarr)
# np.where 的第二个和第三个参数不必是数组 可以是标量值  在数据分析中 where 通常用于根据另外一个数组而产生一个新的数组
# 假设 一个由随机数组组成的矩阵 将所有正值替换成2 将所有负值替换为 -2 使用np.where 函数
arr = np.random.randn(4, 4)
# arr>0 # 布尔数组 判断数组中的每个元素是否大于0
np.where(arr > 0, 2, -2) # 将大于0的值替换为2 小于0的值替换为-2
# np.where 可以将标量和数组结合起来 可以用常数2替换arr中所有正的值 传递给where 的数组大小可以不相等,可以是标量值
a = np.where(arr>0,2,arr) # 将大于0的值替换为2,小于0的值替换为arr

arr = np.random.randn(5, 4) #生成一组随机数组
a = arr.mean()
调用mean()方法对数组 arr中的所有元素计算平均值
mean()方法会将数组中所有元素加起来 然后除以元素的总数 最终结果是一个标量值 单个数组,表示整个数组中所有元素的平均值

# 数学和统计方法 可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算
# sum,mean以及标准std等聚合计算 aggregation 通常叫做约简 既可以当做数组的实例方法调用 也可以当做顶级NumPy函数使用
arr = np.random.randn(5, 4) #生成一组随机数组
# a = arr.mean()
# a = np.mean(arr) # 计算数组中所有元素的平均值
# a = np.sum(arr) # 计算数组中所有元素之和
# mean 和 sum 这类聚合函数可以传递轴向参数 axis 用于计算该轴向上的统计值最终结果是一个少一堆的数组
arr.mean(axis=1) # 计算每一行的平均值
# 调用mean()方法对数组arr 进行计算,但这次axis=1 参数
# axis=1 表示沿着行方向进行计算,即对每一行的数据分别求平均值
arr.sum(axis=0) # 计算每一列的平均值


示例计算
对于数组 arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]：
第 1 个元素：0
第 2 个元素：0 + 1 = 1
第 3 个元素：0 + 1 + 2 = 3
第 4 个元素：0 + 1 + 2 + 3 = 6
第 5 个元素：0 + 1 + 2 + 3 + 4 = 10
依此类推……
最终结果为：

arr = np.array([0,1,2,3,4,5,6,7,8,9,10])
a = arr.cumsum() # 计算数组arr的累计求和
# cumsum和cumprod之类的方法则不聚合 而是产生一个由中间结果组成的数组
# arr.cumsum() 方法对数组 arr进行累计求和 累计求和的规则是：从数组的第一个元素开始，依次将当前元素与之前所有元素的和相加，生成一个新的数组。
# 最终结果是一个与原数组长度相同的数组，表示每个位置的累计和。

sum()
计算数组中所有元素的总和 返回值 一个标量值 表示整个数组或指定轴方向上的元素之和 用于只知道数组中所有元素的总和使用
cumsum()
计算数组的累计和 返回值 一个与原数组形状相同的数组 表示每个位置的累计和  应用场景  当你需要知道每个位置上之前所有元素的累加和使用

arr = np.array([0,1,2,3,4,5,6,7,8,9,10])
a = arr.cumsum() # 计算数组arr的累计求和
# cumsum和cumprod之类的方法则不聚合 而是产生一个由中间结果组成的数组
# arr.cumsum() 方法对数组 arr进行累计求和 累计求和的规则是：从数组的第一个元素开始，依次将当前元素与之前所有元素的和相加，生成一个新的数组。
# 最终结果是一个与原数组长度相同的数组，表示每个位置的累计和。


# 累加函数 cumsum 返回的是同样大小的数组 但是会根据每个低维的切片沿着标记轴计算部分聚类
arr = np.array([[0,1,2],[3,4,5],[6,7,8]])
a = arr.cumsum(axis=0)

按列累计求和 axis=0表示沿着列方向计算
第 1 列：[0, 3, 6]
第 1 个元素：0
第 2 个元素：0 + 3 = 3
第 3 个元素：0 + 3 + 6 = 9
第 2 列：[1, 4, 7]
第 1 个元素：1
第 2 个元素：1 + 4 = 5
第 3 个元素：1 + 4 + 7 = 12
第 3 列：[2, 5, 8]
第 1 个元素：2
第 2 个元素：2 + 5 = 7
第 3 个元素：2 + 5 + 8 = 15
最终结果为：

b = arr.cumsum(axis=1)

sum     对数组中全部或某轴向的元素求和。零长度的数组的sum为0
mean    算术平均数。零长度的数组的mean为NaN
std、var     分别为标准差和方差，自由度可调（默认为n)
min、max     最大值和最小值
argmin、argmax       分别为最大和最小元素的索引
cumsum  所有元素的累计和
cumprod 所有元素的累计积

# 布尔值会被强制转换为 1 True 和 0 False 因此sum 经常被用来对布尔数组的True值计数
arr = np.random.randn(100)
(arr > 0).sum()
# 还有一个布尔值统计函数 any() 和 all() any()用于测试数组中是否存在一个或多个True值
# all则检查数组中所有值是否都是True
bools = np.array([False, False, True, False])
a = bools.any()
b = bools.all()
# 两个方法也能用于非布尔型数组 所有非0元素将会被当做True

# 排序 NumPy数组可以通过sort方法就地排序
arr = np.random.randn(6)
print(arr)
arr.sort()
print(arr)

# 多维数组可以在任何一个轴向上进行排序 只需要将轴编号传给sort方法
arr = np.random.randn(3, 5)
print(arr)
arr.sort(1)
arr.sort(1)
调用 sort() 方法对数组 arr 进行排序。
参数 1 表示沿着行的方向（即每一行内部）进行排序。
排序是就地操作，即直接修改原数组 arr，不会返回新的数组。

如果需要沿着列方向排序，可以将参数改为 0，即 arr.sort(0)。
默认情况下，sort() 方法按升序排序。

large_arr = np.random.randn(1000)
使用 numpy 的 randn 函数生成一个长度为 1000 的一维数组。
randn 函数从标准正态分布（均值为 0，标准差为 1）中随机抽取样本。
结果是一个包含 1000 个浮点数的数组。


large_arr.sort()
调用 sort() 方法对数组 large_arr 进行排序。
排序是就地操作，即直接修改原数组 large_arr，不会返回新的数组。
默认情况下，sort() 方法按升序排序。


large_arr[int(0.05 * len(large_arr))]
计算数组长度的 5% 位置对应的索引：
len(large_arr) 返回数组的长度（1000）。
0.05 * len(large_arr) 计算出 5% 的长度，结果为 50。
int(0.05 * len(large_arr)) 将结果转换为整数，得到索引 50。
索引 50 表示排序后数组中第 51 个元素（索引从 0 开始）。
因此，这行代码的作用是获取排序后数组中前 5% 的值。

# np.in1d 用于测试一个数组中的值在另外一个数组中的成员资格 返回一个布尔数组
values = np.array([6, 0, 0, 3, 2, 5, 6])
使用 numpy 创建一个一维数组 values，内容为 [6, 0, 0, 3, 2, 5, 6]。

print(np.in1d(values, [2, 3, 6]))
调用 numpy 的 in1d 函数。
in1d 的作用是测试第一个数组（values）中的每个元素是否存在于第二个数组（[2, 3, 6]）中。
返回值是一个布尔数组，长度与 values 相同，表示每个元素的测试结果：
如果 values 中的某个元素存在于 [2, 3, 6] 中，则对应位置返回 True。
否则返回 False。
示例计算过程
对于数组 values = [6, 0, 0, 3, 2, 5, 6] 和 [2, 3, 6]：
第 1 个元素：6 存在于 [2, 3, 6] 中 → 返回 True。
第 2 个元素：0 不存在于 [2, 3, 6] 中 → 返回 False。
第 3 个元素：0 不存在于 [2, 3, 6] 中 → 返回 False。
第 4 个元素：3 存在于 [2, 3, 6] 中 → 返回 True。
第 5 个元素：2 存在于 [2, 3, 6] 中 → 返回 True。
第 6 个元素：5 不存在于 [2, 3, 6] 中 → 返回 False。
第 7 个元素：6 存在于 [2, 3, 6] 中 → 返回 True。
最终结果为：
[True, False, False, True, True, False, True]
unique(x)   计算x中的唯一元素，并返回有序结果
intersect1d(x,y)    计算x和y中的公共元素，并返回有序结果
union1d(x,y)    计算x和y的并集，并返回有序结果
in1d(x, y)  得到一个表示“x的元素是否包含于y”的布尔型数组
setdiff1d(x,y)  集合的差，即元素在x中且不在y中
setxor1d(x,y)   集合的对称差，即存在于一个数组中但不同时存在于两个数组中的元素

# np.unique 函数用于返回一个数组中所有唯一元素，并返回一个已排序的数组
# 该数组包含所有唯一元素，以及一个布尔数组，该布尔数组表示原始数组中每个元素是否是唯一的。

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
print(np.unique(names))
# 等价的纯python代码
# sorted(set(names))

ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
print(np.unique(ints))

# np.in1d 用于测试一个数组中的值在另外一个数组中的成员资格 返回一个布尔数组
values = np.array([6, 0, 0, 3, 2, 5, 6])
print(np.in1d(values, [2, 3, 6]))

用数组的文件输入输出
np.save和 np.load 是读写磁盘数组数据的两个主要函数 数组是以未压缩的原始二进制格式在扩展名为 .npy的文件中

arr = np.arange(10)
# np.save('some_array',arr)  # 保存为some_array.npy

np.load('some_array.npy')  # 加载some_array.npy
np.savez('array_archive.npz', a=arr, b=arr)
arch = np.load('array_archive.npz')
print(arch['b'])
# 将数据压缩
np.savez_compressed('arr_compressed.npz', a=arr, b=arr)

线性代数 如矩阵乘法,矩阵分解,行列式 是任何数组库的重要组成部分 通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积
NumPy 提供了一个用于矩阵乘法的dot函数 即是一个数组方法也是numpy命名中的一个函数

x = np.array([[1.,2.,3.],[4.,5.,6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
# 矩阵乘法 x.dot(y) == np.dot(x, y)
x.dot(y)
矩阵乘法的规则是 第一个矩阵的行与第二个矩阵的列对应元素相乘并求和
结果矩阵形状为(2,2)  因为x的形状是(2,3) y的形状是(3,2)

第 1 行 × 第 1 列：
(1 * 6) + (2 * -1) + (3 * 8) = 6 - 2 + 24 = 28
第 1 行 × 第 2 列：
(1 * 23) + (2 * 7) + (3 * 9) = 23 + 14 + 27 = 64
第 2 行 × 第 1 列：
(4 * 6) + (5 * -1) + (6 * 8) = 24 - 5 + 48 = 67
第 2 行 × 第 2 列：
(4 * 23) + (5 * 7) + (6 * 9) = 92 + 35 + 54 = 181

一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后将会得到一个一维数组
x = np.array([[1.,2.,3.],[4.,5.,6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
# 矩阵乘法 x.dot(y) == np.dot(x, y)
x.dot(y)
np.dot(x,np.ones(3))
# @符号 也可以用作 中缀运算符 进行矩阵乘法
x @ np.ones(3)



from numpy.linalg import inv,qr

X = np.random.randn(5,5)
使用 numpy 的 randn 函数生成一个形状为 (5, 5) 的二维数组。
randn 函数从标准正态分布（均值为 0，标准差为 1）中随机抽取样本。
结果是一个包含 5 行 5 列的浮点数数组。


mat = X.T.dot(X)
计算矩阵 X 的转置矩阵 X.T 与矩阵 X 的矩阵乘积。
X.T 是矩阵 X 的转置，即将行变为列，列变为行。
dot 方法用于矩阵乘法。
结果是一个对称矩阵 mat，其形状为 (5, 5)。


inv(mat)
调用 numpy.linalg.inv 函数计算矩阵 mat 的逆矩阵。
逆矩阵的定义是：如果矩阵 A 是可逆的，则存在一个矩阵 B，使得 A.dot(B) == B.dot(A) == I（单位矩阵）。
注意：只有当矩阵是非奇异（行列式不为零）时，才能计算其逆矩阵。

mat.dot(inv(mat))
计算矩阵 mat 与其逆矩阵的矩阵乘积。
理论上，结果应该是一个单位矩阵（对角线元素为 1，其余元素为 0），但由于数值计算误差，结果可能略有偏差。

q,r = qr(mat)
调用 numpy.linalg.qr 函数对矩阵 mat 进行 QR 分解。
QR 分解是一种将矩阵分解为两个矩阵的方法：
Q 是一个正交矩阵（Q.T.dot(Q) == I）。
R 是一个上三角矩阵。
结果是两个矩阵 q 和 r，满足 mat == q.dot(r)。

diag
以一维数组的形式返回方阵的对角线（或非对角线）元素，或将一维数组
转换为方阵（非对角线元素为0）
dot
矩阵乘法
trace
计算对角线元素的和
det
计算矩阵行列式
eig
计算方阵的本征值和本征向量
inv
计算方阵的逆
pinv
计算矩阵的Moore-Penrose伪逆
qr
计算QR分解
svd
计算奇异值分解(SVD)
solve
解线性方程组Ax=b,其中A为一个方阵
Istsq
计算Ax=b的最小二乘解

伪随机数生成
numpy.random模块对python内置的random进行补充,增加了一些用于高效生成多种概率分布的样本值的函数

# normal来得到一个标准正态分布的4*4样本数组
samples = np.random.normal(size=(4,4))
使用 numpy.random.normal 函数生成一个形状为 (4, 4) 的二维数组。
np.random.normal 是 NumPy 提供的一个函数，用于生成符合正态分布（高斯分布）的随机数。
参数 size=(4, 4) 指定生成的数组形状为 (4, 4)，即包含 4 行 4 列的浮点数。
默认情况下，生成的随机数服从标准正态分布（均值为 0，标准差为 1）

print(samples)

from random import normalvariate
from random import normalvariate
从 Python 标准库中的 random 模块导入 normalvariate 函数。
normalvariate(mu, sigma) 用于生成单个符合正态分布的随机数。
参数 mu 表示均值，sigma 表示标准差。

N = 1000000
定义变量 N，表示生成随机数的数量，这里设置为 100 万。

%timeit samples = [normalvariate(0,1) for _ in range(N)]
使用 IPython 的魔法命令 %timeit 测量生成 100 万个随机数所需的时间。
[normalvariate(0,1) for _ in range(N)] 是一个列表推导式，
通过循环调用 normalvariate(0, 1) 生成 100 万个随机数。
这种方法基于 Python 内置的 random 模块，每次生成一个随机数，效率较低。

%timeit np.random.normal(size=N)
同样使用 %timeit 测量生成 100 万个随机数所需的时间。
np.random.normal(size=N) 是 NumPy 提供的向量化方法，一次生成指定数量的随机数。
这种方法利用了 NumPy 的矢量化计算能力，通常比纯 Python 的循环方式快得多。

这些都是伪随机数 是因为它们都是通过算法基于随机数生成器种子 在确定性的条件下生成的
可以用 NumPy的 np.random.seed 更改随机数生成种子
np.random.seed(1234)

numpy.random的数据生成函数使用了全局的随机种子。要避免全局状态，你可以使用numpy.random.RandomState，创建一个与其它隔离的随机数生成器：
In [245]: rng = np.random.RandomState(1234)
In [246]: rng.randn(10)
Out[246]:
array([ 0.4714, -1.191 ,  1.4327, -0.3127, -0.7206,  0.8872,  0.8596,
       -0.6365,  0.0157, -2.2427])

seed
确定随机数生成器的种子
permutation
返回一个序列的随机排列或返回一个随机排列的范围
shuffle
对一个序列就地随机排列
rand
产生均匀分布的样本值
randint
从给定的上下限范围内随机选取整数
randn
产生正态分布（平均值为0，标准差为1)的样本值，类似于MATLAB接口
binomial
产生二项分布的样本值
normal
产生正态（高斯）分布的样本值
beta
产生Beta分布的样本值

chisquare
产生卡方分布的样本值
gamma
产生Gamma分布的样本值
uniform
产生在[0,1)中均匀分布的样本值

随机漫步 来说明如何运用数组运算 简单的随机漫步的例子 从0开始 步长1 和 -1出现的概率相等
import random


position = 0
walk = [position]
steps = 1000
for i in range(steps):
    step = 1 if random.randint(0,1) else -1
    position += step
    walk.append(position)

print(plt.plot(walk[:100]))
不难看出，这其实就是随机漫步中各步的累计和，可以用一个数组运算来实现。
因此，我用np.random模块一次性随机产生1000个“掷硬币”结果（即两个数中任选一个），将其分别设置为1或－1，然后计算累计和：

这段代码实现了一个简单的随机漫步（Random Walk）模拟，以下是逐步解释：
1. nsteps = 1000
定义变量 nsteps，表示随机漫步的步数，这里设置为 1000 步。
2. draws = np.random.randint(0, 2, size=nsteps)
使用 numpy.random.randint 函数生成一个长度为 nsteps 的一维数组。
参数说明：
0 和 2 表示生成的随机整数范围为 [0, 2)，即可能的值为 0 或 1。
size=nsteps 指定生成的数组长度为 nsteps（1000）。
结果是一个包含 1000 个随机整数（0 或 1）的数组，表示每次随机漫步的方向。
3. steps = np.where(draws > 0, 1, -1)
使用 numpy.where 函数根据条件对数组 draws 进行转换。
条件 draws > 0：如果 draws 中的元素大于 0，则返回 True，否则返回 False。
转换规则：
如果条件为 True，返回 1。
如果条件为 False，返回 -1。
结果是一个长度为 1000 的数组 steps，其中每个元素表示一步的方向（1 表示向前，-1 表示向后）。
4. walk = steps.cumsum()
调用 cumsum() 方法对数组 steps 进行累计求和。
累计求和的规则是：从数组的第一个元素开始，依次将当前元素与之前所有元素的和相加，生成一个新的数组。
结果是一个长度为 1000 的数组 walk，表示随机漫步过程中每一步的位置。


"""
import numpy as np
import matplotlib.pyplot as plt
from Crypto.Random.random import sample
from numpy.linalg import inv,qr
from numpy.random import normal
import random


position = 0
walk = [position]
steps = 1000
for i in range(steps):
    step = 1 if random.randint(0,1) else -1
    position += step
    walk.append(position)

print(plt.plot(walk[:100]))

if __name__ == '__main__':
    pass
